﻿using System;
using System.Diagnostics.Contracts;
using FlitBit.Core.Parallel;

namespace FlitBit.Cache
{
	/// <summary>
	///   Extensions over ICache&lt;,>
	/// </summary>
	public static class CacheExtensions
	{
		/// <summary>
		///   Gets a value from the cache, blocking the calling thread
		///   until the cache returns a result.
		/// </summary>
		/// <typeparam name="TKey">key type K</typeparam>
		/// <typeparam name="TValue">item type V</typeparam>
		/// <param name="cache">the cache</param>
		/// <param name="key">the item's key</param>
		/// <returns>the item's cached value</returns>
		public static TValue Get<TKey, TValue>(this ICache<TKey, TValue> cache, TKey key)
		{
			Contract.Requires(!cache.IsDisposed);
			using (var future = new Future<Tuple<bool, TValue>>())
			{
				cache.Get(key, (e, res) =>
				{
					if (e != null)
					{
						future.MarkFaulted(e);
					}
					else if (res == null)
					{
						future.MarkCompleted(Tuple.Create(false, default(TValue)));
					}
					else
					{
						future.MarkCompleted(Tuple.Create(true, res.Item2));
					}
				});
				var result = future.Value;
				return result.Item2;
			}
		}

		/// <summary>
		///   Puts a value into the cache, blocking the calling thread
		///   until the cache returns a confirmation.
		/// </summary>
		/// <typeparam name="K">key type K</typeparam>
		/// <typeparam name="V">item type V</typeparam>
		/// <param name="cache">the cache</param>
		/// <param name="key">the item's key</param>
		/// <param name="value">the item's value</param>
		/// <returns>the item's value</returns>
		public static V Put<K, V>(this ICache<K, V> cache, K key, V value)
		{
			Contract.Requires(!cache.IsDisposed);
			using (var future = new Future<Tuple<K, V>>())
			{
				cache.Put(key, value, (e, res) =>
				{
					if (e != null)
					{
						future.MarkFaulted(e);
					}
					else
					{
						future.MarkCompleted(res);
					}
				});
				var result = future.Value;
				return (result != null) ? result.Item2 : default(V);
			}
		}

		/// <summary>
		///   Tries to get a value from the cache, blocking the calling thread
		///   until the cache returns a result.
		/// </summary>
		/// <typeparam name="K"></typeparam>
		/// <typeparam name="V"></typeparam>
		/// <param name="cache"></param>
		/// <param name="key"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public static bool TryGet<K, V>(this ICache<K, V> cache, K key, out V value)
		{
			Contract.Requires(!cache.IsDisposed);
			using (var future = new Future<Tuple<bool, V>>())
			{
				cache.Get(key, (e, res) =>
				{
					if (e != null)
					{
						future.MarkFaulted(e);
					}
					else if (res == null)
					{
						future.MarkCompleted(Tuple.Create(false, default(V)));
					}
					else
					{
						future.MarkCompleted(Tuple.Create(true, res.Item2));
					}
				});
				var result = future.Value;
				value = result.Item2;
				return result.Item1;
			}
		}

		/// <summary>
		///   Tries to remove a value from the cache, blocking the calling thread
		///   until the cache returns a result.
		/// </summary>
		/// <typeparam name="K"></typeparam>
		/// <typeparam name="V"></typeparam>
		/// <param name="cache"></param>
		/// <param name="key"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public static bool TryRemove<K, V>(this ICache<K, V> cache, K key, out V value)
		{
			Contract.Requires(!cache.IsDisposed);
			using (var future = new Future<Tuple<bool, V>>())
			{
				cache.Remove(key, (e, res) =>
				{
					if (e != null)
					{
						future.MarkFaulted(e);
					}
					else if (res == null)
					{
						future.MarkCompleted(Tuple.Create(false, default(V)));
					}
					else
					{
						future.MarkCompleted(Tuple.Create(true, res.Item2));
					}
				});
				var result = future.Value;
				value = result.Item2;
				return result.Item1;
			}
		}
	}
}